Pod racunsko geometrijo (computational geometry) si predstavljamo
prizadevanja, da bi resevali geometrijske probleme z racunalnikom.
Tu je eden od najbolj zoprnih problemov ta, da moramo obicajno
delati s stevili v plavajoci vejici in se zato otepati z 
racunskimi in zaokrozitvenimi napakami.  Zato je npr. pametno, 
da nikoli ne preverjamo, ce sta dve stevili enaki, ampak raje 
preverjamo, ce se razlikujeta za manj kot eps (za neko majhno 
vrednost eps, npr. eps = 1e-7 ali kaj podobnega -- odvisno od 
tega, kaj tocno pocnemo).

O racunski geometriji ne vem pretirano veliko, zato bom tule
napisal le osnovnih nekaj stvari.  Do nadaljnjega predpostavimo, 
da delamo v 2-d ravnini in je vsaka tocka opisana z vektorjem
(parom koordinat) (x, y).  Z vektorjem lahko opisemo tudi
premik od ene tocke do druge, npr. od (x1, y1) do (x2, y2)
z vektorjem (x2-x1, y2-y1) -- to nam pove, kako lezi druga
tocka glede na prvo; lahko bi tudi rekli, da smo opisali smer.

--

Recimo, da imamo tocki A(x1, y1) in B(x2, y2).  Ti dve nam
dolocata neko premico.  Zdaj bi bili radi za poljubno tocko C(x, y)
sposobni povedati, na kateri strani te premice lezi.

To lahko elegantno naredimo s pomocjo vektorskega produkta.
Mislimo si, da te nase tocke lezijo v 3-d prostoru na ravnini
z=0.  Imamo torej (x1, y1, 0), (x2, y2, 0) in (x, y, 0).
Glejmo vektorja (x2-x1, y2-y1, 0) (za premik od A do B) in 
(x-x1, y-y1, 0) (premik od A do C).  Po formuli za vektorski 
produkt lahko izracunamo, da je njun vektorski produkt kar
(0, 0, (x2-x1)*(y-y1) - (y2-y1)*(x-x1)).  Spomnimo se, kako
je definiran vektorski produkt dveh vektorjev: vedno je
pravokoten na njiju (OK, to je v nasem primeru tudi res, saj
prvotna vektorja lezita v ravnini z=0, nas produkt pa je
v smeri z, torej pravokoten na to ravnino), njegova dolzina
je dolocena s ploscino paralelograma, ki ga tvorita prvotna
vektorja, njegova smer pa je dolocena s pravilom desne roke
(oz. desnega vijaka).  V nasem konkretnem primeru to pomeni,
da kaze dobljeni vektorski produkt nad ravnino z=0 (torej ima
pozitivno z-koordinato), ce gre premik od B-A do C-A v 
pozitivni smeri (= nasprotni smeri urinega kazalca),
ce pa gre ta premik v negativni smeri (= v smeri urinega
kazalca), ima vektorski produkt negativno z-koordinato.  Ce
pa iam vektorski produkt koordinato 0, je moral imeti 
paralelogram, ki ga oklepata B-A in C-A, ploscino 0, kar
pomeni, da sta B-A in C-A vzporedna, torej lezi C prav na
premici, ki gre skozi tocki A in B.

function Orientacija(xA, yA, xB, yB, xC, yC: Real): Integer;
var Z: Real;
begin
  Z := (xB-xA)*(yC-yA) - (yB-yA)*(xC-xA);
  if Z > Eps then Orientacija := 1
  else if Z < -Eps then Orientacija := -1
  else Orientacija := 0;
end;

Zdaj lahko tudi preprosto preverimo, ce lezi tocka C
na premici skozi A in B:

function AliLeziNaPremici(xA, yA, xB, yB, xC, yC: Real): Boolean;
begin
  AliLeziNaPremici := Orientacija(xA, yA, xB, yB, xC, yC) = 0;
end;

Ce bi hoteli se preveriti, ali lezi na _daljici_ od A do B,
bi lahko postavili se dodatni pogoj, da mora biti xC
med xA in xB ter da mora biti yC med yA in yB:

function AliLeziNaDaljici(xA, yA, xB, yB, xC, yC: Real): Boolean;
begin
  AliLeziNaDaljici := 
    (Min(xA, xB) - Eps <= xC) and (xC <= Max(xA, xB) + Eps) and
    (Min(yA, yB) - Eps <= yC) and (yC <= Max(yA, yB) + Eps) and
   AliLeziNaPremici(xA, yA, xB, yB, xC, yC);
end;

Da preverimo, ce se sekata (ali vsaj dotikata) dve daljici, 
recimo AB in CD, se izkaze, da je koristno vzeti naslednje pogoje:
1 ce okoli vsake daljice ocrtamo pravokotnik (s stranicami, 
  vzporednimi s koordinatnimi osmi), se morata pravokotnika obeh
  daljic sekati (ali pa vsaj dotikati);
2 tocka C mora biti na drugi strani premice AB kot tocka D
  (no, ni nujno, da strogo na drugi strani; lahko katera
  tudi lezi na premici AB);
3 tocka A mora biti na drugi strani premice CD kot tocka B.

function AliSeDaljiciSekata(xA, yA, xB, yB,
             xC, yC, xD, yD: Real): Boolean;
var xxA, yyA, xxB, yyB, xxC, yyC, xxD, yyD, p1, p2: Real;
begin
  AliSeDaljiciSekata := False; 

  (* pogoj 1 *)
  xxA := Min(xA, xB); xxB := Max(xA, xB);
  xxC := Min(xC, xD); xxD := Max(xC, xD);
  yyA := Min(yA, yB); yyB := May(yA, yB);
  yyC := Min(yC, yD); yyD := May(yC, yD);
  if (xxB < xxC - Eps) or (xxA > xxD + Eps)
  or (yyB < yyC - Eps) or (yyA > yyD + Eps) then Exit;

  (* pogoj 2 *)
  p1 := (xC-xA)*(yB-yA) - (yC-yA)*(xB-xA);
  p2 := (xD-xA)*(yB-yA) - (yD-yA)*(xB-xA);
  if ((p1 > Eps) and (p2 > Eps))
  or ((p1 < -Eps) and (p2 < -Eps)) then Exit;

  (* pogoj 2 *)
  p1 := (xA-xC)*(yD-yC) - (yA-yC)*(xD-xC);
  p2 := (xB-xC)*(yD-yC) - (yB-yC)*(xD-xC);
  if ((p1 > Eps) and (p2 > Eps))
  or ((p1 < -Eps) and (p2 < -Eps)) then Exit;
  
  AliSeDaljiciSekata := True;
end;

Dobro pri vseh teh postopkih je, da se nam ni treba ubadati
z racunanjem koordinat tocke, v kateri se daljici sekata --
vcasih je dovolj vedeti, se ce sekata, ne zanima pa nas, kje.

Ocitno bi lahko vektorski produkt uporabili tudi za
preverjanje vzporednosti.  Ce imamo vektor, ki kaze v
smeri ene premice (recimo AB), in se enega za drugo
premico (recimo CD), sta premici vzporedni, ce je
vektorski produkt teh dveh vektorjev enak 0.  No, kot obicajno
bomo namesto enakosti 0 raje preverili, ce je dovolj blizu 0.

function AliStaDaljiciVzporedni(xA, yA, xB, yB,
             xC, yC, xD, yD: Real): Boolean;
begin
  AliStaDaljiciVzporedni :=
    Abs((xB-xA)*(yD-yC) - (yB-yA)*(xD-xC)) < Eps;
end;

Se ena zanimiva stvar je projekcija tocke na premico.
Ce imamo neko tocko C in neko premico p (ki gre skozi
tocki A in B), je projekcija C na p tista tocka (recimo 
ji D), ki je med vsemi tockami s premice p najblizja 
tocki C.  Prepoznamo jo po tem, da se, ce gremo od D do C, 
gibljemo pravokotno na premico p (ce se ne bi, bi obstajala 
na p kaksna tocka, ki je se blizje C-ju kot D).
Spomnimo se, da lahko pravokotnost vektorjev preverjamo
s skalarnim produktom: skalarni produkt pravokotnih vektorjev
je vedno enak 0.
  Vse tocke na premici skozi A in B imajo koordinate oblike:
(xA + t*(xB-xA), yA + t*(yB-yA)) in ko gre t po vseh realnih
stevilih, dobimo ravno vse tocke z nase premice.  Ena med 
njimi je torej tudi tocka D, saj tudi ta lezi na premici p;
poiskati moramo le pravo vrednost t-ja za tocko D.  Tu pa 
si bomo pomagali s pogojem iz prejsnjega odstavka, da
mora biti vektor od D do C pravokoten na premico p; ta
pogoj je enakovreden temu, ce bi rekli, da mora biti
pravokoten na vektor od A do B (saj ravno ta doloca smer 
premice p).  Imamo torej zahtevo:
  (xC-xA-t*(xB-xA), yC-yA-t*(yB-yA)) pravokoten na (xB-xA, yB-yA).
Pravokotnost izrazimo s skalarnim produktom:
  ((xC-xA-t*(xB-xA))*(xB-xA) + (yC-yA-t*(yB-yA))*(yB-yA) = 0
In iz tega izrazimo t:
  t = [(xC-xA)(xB-xA) + (yC-yA)(yB-yA)] / [(xB-xA)^2 + (yB-yA)^2].
(Seveda je lahko skalarni produkt obeh vektorjev enak 0 tudi
v primeru, ce ima kaksen od vektorjev dolzino 0.  Takrat je
mogoce manj smiselno govoriti o tem, da sta res pravokotna;
ampak kakorkoli ze, v nasem primeru (xB-xA, yB-yA) gotovo nima
dolzine 0, saj bi to pomenilo, da sta A in B ena in ista tocka
in nam torej ne bi enolicno dolocali neke premice p; vektor
od D do C pa je lahko dolzine 0, vendar ni s tem nic narobe,
saj to pomeni le, da C pac lezi na premici p in je zato kar sam
svoja projekcija na to premico.)
  Ni se nam treba bati, da bi pri racunanju t-ja delili z 0,
saj imamo v imenovalcu kar kvadrat razdalje od A do B (po 
Pitagorovem izreku), to pa bo menda ja vecje od 0, ce sta A in
B res dve razlicni tocki.
  Skratka, zdaj ko imamo t, lahko izracunamo koordinate projekcije:
    xD := xA + t * (xB-xA);    yD := yA + t * (yB-yA);

Ce bi nas zanimala razdalja med tocko C in premico AB, je to
v bistvu kar razdalja med C in njeno projekcijo na to premico.
Pomagali bi si torej z razmislekom iz prejsnjega odstavka.
Ta razmislek bi bil koristen tudi pri preverjanju, ce se premica
seka z neko kroznico.

Preverjanje, ce se sekata dve kroznici, je seveda se enostavnejse:
razdalja med njunima srediscema mora biti manjsa od vsote njunih
radijev; ce pa tu velja enakost, se kroznici dotikata.

Mogoce lahko kdaj pride prav ploscina poljubnega mnogokotnika.
Recimo, da so njegova oglisca (po vrsti) tocke (x1, y1), ...,
(xN, yN).  Za potrebe enostavnejsega zapisa si mislimo se,
da imamo x0 = xN in y0 = yN.  Potem si lahko za dve zaporedni
tocki, npr. (x[i-1], y[i-1]) in (x[i], y[i]), mislimo trapez,
ki ga poleg teh dveh sestavljata se tocki (x[i-1], 0) in (x[i], 0).
Ploscina tega trapeza je (x[i] - x[i-1]) * (y[i-1] + y[i])/2.
Ce zdaj sestejemo te ploscine po vseh trapezih, torej ko gre
i od 1 do N, dobimo ravno ploscino celega mnogokotnika.  Zakaj?
Ko obiskujemo oglisca na zgornjem robu mnogokotnika, nam ploscine
trapezov, ki jih racunamo tisti cas, pokrivajo ne le celega
mnogokotnika, pac pa tudi prostor pod njim vse do x-osi.  Ko pa
obiskujemo oglisca na spodnjem robu mnogokotnika, nam ploscine
tukajsnjih trapezov pokrivajo le prostor pod mnogokotnikom vse do
x-osi.  In ker imamo oglisca podana po vrsti, se pri eni od teh
dveh skupin premikamo po narascajocih x-koordinatah (torej v desno),
pri drugi pa v levo, zato bodo ploscine, ki jih bomo dobivale,
v enem primeru nasprotno predznacene kot v drugem.  To pa v 
resnici pomeni, da se nam bo povrsina pod mnogokotnikom (se pravi:
med njim in x-osjo) odstela od tistega, kar bomo naracunali pri
prvi skupini tock (povrsino celega mnogokotnika in se prostora
pod njim vse do x-osi), tako da bo preostala razlika ravno
ploscina mnogokotnika.  No, mozno je, da bo koncni rezultat
negativen, kar pa pomeni le, da smo obiskali mnogokotnik ravno v
nasprotni smeri urinega kazalca in moramo zdaj vzeti absolutno
vrednost dobljenega rezultata.  V resnici isti razmislek deluje
tudi za primere, ko je mnogokotnik bolj cudne oblike in se nam
x-koordinate nekaj casa povecujejo, nekaj casa zmanjsujejo, pa
spet povecujejo in spet zmanjsujejo itd.

--

http://www.cse.unsw.edu.au/~lambert/java/3d/hull.html
http://www.cs.princeton.edu/~ah/alg_anim/version1/ConvexHull.html

Se ena koristna zadeva je konveksna ovojnica.  Pravimo, da je nek
lik konveksen (po domace: izbocen), ce za vsak par tock A in B,
ki ju vsebuje ta lik, pripada liku tudi cela daljica od A do B.
V primeru mnogokotnikov (torej likov, ki jih omejujejo same ravne
stranice in se po moznosti tudi ne sekajo) je to enakovredno
zahtevi, da morajo biti notranji koti pri vseh ogliscih manjsi 
od 180 stopinj.  "Konveksna ovojnica" (convex hull) neke mnozice
tock je najmanjsi konveksen lik, ki vsebuje vse te tocke.  To si
je mogoce zelo enostavno predstavljati tako, da si tocke iz dane
mnozice mislimo kot zeblje, zabite v desko, nato pa okoli vseh teh
zebljev napenjamo elastiko.  Elastika se bo skrcila ravno v rob
najmanjsega konveksnega lika, ki pokriva vse nase zeblje.
Na tej zamisli temelji tudi cisto konkreten algoritem za racunanje
konveksne ovojnice (Graham scan):
- V dani mnozici tock poiscimo recimo najbolj spodnjo (torej
  tisto z najmanjso y-koordinato), ce pa je vec takih, 
  vzemimo med njimi tisto z najmanjso x-koordinato.  
  Recimo dobljeni tocki (x0, y0).
- Ostale tocke uredimo glede na kot, pod katerim jih vidimo
  iz tocke (x0, y0).  Tocki (x[i], y[i]) bi torej pripisali
  kot arctg (y[i]-y0)/(x[i]-x0).  Kote si raje mislimo 
  od 0 do 180 stopinj oz. od 0 do pi radianov, ne 
  npr. od -90 do +90.  Ko smo tocke tako uredili, jih v
  mislih oznacimo od (x[1], y[1]) do (x[N-1], y[N-1]).
- Tocka (x0, y0) gotovo lezi na robu konveksne ovojnice, saj
  je najnizja in med najnizjimi najbolj leva, tako da je 
  nemogoce, da bi se ji nasa elastika izognila.
  Za zacetek vzemimo tudi, da na robu konveksne ovojnice
  lezi (x1, y1).
- Pojdimo z i od 2 do N:
  - Recimo, da bi tudi tocka (x[i], y[i]) lezala na robu
    konveksne ovojnice.  Naj bo (x[j], y[j]) prejsnja
    tocka na robu konveksne ovojnice (kot bomo videli, ni
    nujno j = i-1, ampak je lahko j tudi manjsi),
    (x[k], y[k]) pa predprejsnja.
    Rob konveksne ovojnice je konveksen mnogokotnik, zato 
    mora biti kot pri ogliscu (x[j], y[j]) manjsi od 180
    stopinj.  Ker se premikamo po robu konveksne ovojnice v
    smeri, nasprotni urinega kazalca, to tudi pomeni, da
    mora biti premik iz smeri (x[j]-x[k], y[j]-y[k])
    v smer (x[i]-x[j], y[i]-y[j]) v bistvu premik nasproti
    smeri urinega kazalca.  Spet si lahko pomagamo z 
    vektorskim produktom, ki mora biti v tem primeru
    pozitiven (zaradi pravila desne roke oz. desnega vijaka).
    Torej, ce vrednost
      (x[j]-x[k])*(y[i]-y[j]) - (y[j]-y[k])*(x[i]-x[j])
    ni pozitivna, potem tocka (x[j], y[j]) v resnici ne 
    more lezati na robu konveksne ovojnice, ker bi bil
    v tem primeru notranji kot pri njej prevelik.  
    (To si lahko predstavljamo takole: najprej smo peljali
    elastiko do tocke k in nato do tocke j; potem pa smo
    konec elastike premaknili do tocke i in opazili, da
    se zdaj ne dotika vec tocke j, ker si je nasla krajso
    pot naravnost od k do i.)  V tem primeru se torej
    odlocimo, da j v resnici ne lezi na robu konveksni
    ovojnici (pac pa v notranjosti), zato pa na rob
    konveksne ovojnice dodajmo tocko i (mogoce jo bomo
    kdaj kasneje se zavrgli, tako kot smo zdajle tocko j).
V zadnji iteraciji, pri i = N, je misljeno, da namesto
(x[i], y[i]) gledamo spet (x[0], y[0]), saj za to vemo, da
mora biti na robu konveksne ovojnice, lahko pa v tem koraku 
se zbrisemo kaksno tocko, za katero smo prej mislili, da bo
na robu, pa se izkaze, da je v resnici v notranjosti.
  Tocke, ki lezijo na robu konveksne ovojnice, je pametno
hraniti v kaksni tabeli ali seznamu (oz. skladu), tako da 
lahko brez tezav dodajamo ali brisemo tocke na koncu 
seznama.  Na koncu nam ostanejo v seznamu ravno tocke, ki
lezijo na robu konveksne ovojnice, in to se v pravem vrstnem
redu za povrhu (rob prehodimo v smeri, nasprotni smeri
urinega kazalca).

Konveksna ovojnica pride na primer prav pri nalogi s skrati
in avtocestami z letosnje CEOI:
  http://cs.science.upjs.sk/ceoi/History.html#2002-21
Tu se v bistvu sprasuje, ali dana premica seka konveksno
ovojnico dane mnozice tock.  Vsekakor je izracun konveksne
ovojnice dober prvi korak, saj nam pokaze, da je dovolj
gledati, ce premica seka rob te ovojnice, za tocke v 
notranjosti pa se ni treba zanimati.  Res pa je, da so lahko
tocke razpostavljene npr. po obodu kroznice, tako da pridejo
v resnici vse na rob konveksne ovojnice.  Zato konveksna
ovojnica sama po sebi pri tej nalogi se ni dovolj.  Moznost
bi bila npr. pogledati, na kateri strani premice lezi prva
tocka, potem pa iskati, ce lezi kaksna slucajno tudi na drugi
strani.  V ta namen bi lahko tocke razporedili v kaksne skupine, 
ocrtali vsaki skupini nek pravokotnik (vzporeden s koordinatnimi
osmi) in preverjali, ce vsaj kaksno oglisce tega pravokotnika
lezi na zeljeni strani premice; ce nobeno, potem tock v tem
pravokotniku sploh ni treba preverjati.  Tako bi verjetno
prihranili kar precej casa in mogoce nalogo resili dovolj
hitro.  Avtorji naloge pa so objavili tudi neko resitev, ki
(po racunanju konveksne ovojnice) temelji na bolj elegantnem 
geometrijskem razmisleku.  Ta hip knjizice z resitvami ne 
najdem na njihovem web siteu, ampak ce jo bom nasel, bom o 
priliki poslal tudi URL na tisto resitev.

--

Pa recimo, da imamo nek konveksen mnogokotnik in nas zanima,
ce dana tocka lezi v njem.  Recimo, da imamo podane koordinate
oglisc tega mnogokotnika lepo po vrsti, npr. nasproti smeri
urinih kazalcev: (x[1], y[1]), ..., (x[N], y[N]).  Potem 
lahko za dano tocko (X, Y) preverimo, ce lezi v mnogokotniku, 
preprosto tako, da preverimo, ce za vsak i (od 1 do N)
lezi "znotraj" glede na stranico od (x[i-1], y[i-1]) do
(x[i], y[i]).  To spet naredimo z vektorskim produktom --
(x[i]-x[i-1])*(Y-y[i-1]) - (y[i]-y[i-1])*(X-x[i-1]) mora
biti pozitiven.

Ce mnogokotnik ni konveksen, ampak je bolj cudne oblike,
mogoce celo tak, ki seka sam svoje stranice, si lahko pomagamo
z naslednjim (malo bolj zapletenim) postopkom: posljimo iz
nase tocke (X, Y) poltrak v poljubni smeri.  Ce bi se zdaj
gibali po tem poltraku, se v vsakem trenutku, ko sekamo katero
od stranic nasega mnogokotnika, spremeni stanje tega, ali
smo znotraj ali zunaj.  Na koncu pa bomo seveda ostali zunaj, 
saj je mnogokotnik omejen, poltrak pa neskoncen.  Torej, ce ta 
poltrak k-krat seka stranice mnogokotnika in je k sodo
stevilo, smo bili tudi na zacetku zunaj in tocka (X, Y) ni
lezala v notranjosti mnogokotnika; ce je bil k lih, pa je
lezala v notranjosti.  Verjetno je najbolj enostavno, ce
gre ta poltrak npr. kar v vodoravni smeri, saj je potem lazje
preveriti, ce ga dolocena stranica seka ali ne.  Ce hocemo 
biti res previdni, pa moramo paziti tudi na moznost, da 
katero od oglisc, mogoce pa celo vec zaporednih oglisc,
lezi na tem poltraku; v tem primeru je treba gledati, ali
oglisci pred in za tistimi na poltraku lezita na isti 
strani poltraka ali ne -- ce ne, potem velja, kot da je
poltrak (enkrat) sekal rob mnogokotnika.  No, ker ima 
mnogokotnik le koncno mnogo oglisc, moznih smeri poltraka pa
je neskoncno, lahko vedno tudi izberemo tako smer poltraka,
da ne bo na njem lezalo nobeno oglisce.

--

Kot receno, jaz o racunski geometriji ne vem pretirano veliko, 
pa tudi nikoli me ni pretirano zanimala, zato se bom tule 
ustavil.  Ce se bo kdo zelo navdusil za racunsko geometrijo, 
mu priporocam knjigo "Computational Geometry: An Introduction"
(avtorja: Preparata in Shamos).

Tule je nekaj nalog, pri katerih pride prav nekaj geometrije:

http://acm.uva.es/p/v3/361.html
http://acm.uva.es/p/v4/438.html
http://acm.uva.es/p/v4/453.html
http://acm.uva.es/p/v4/478.html
